#include<iostream>
#include<sys/socket.h> //socket
#include<sys/epoll.h> //epoll
#include<unistd.h>
#include<arpa/inet.h> //n to h 
#include<cstring>
#include<fcntl.h>
using namespace std;

//这里是一个单线程+epoll的简单服务器实现,
//也是一个简单的I/O多路复用的例子,
//通过给一个线程创建EPOLL实例实现对多个文件描述符的监听(如果不懂这句话可以去看看file.md,里面有讲解)
/*
这里用的是 ubuntu   12th Gen Intel(R) Core(TM) i5-12500H
先看一下结果，用wrk跑一万并发链接能达到20080的QPS  
wwh@freshman001:~$ wrk -t12 -c10000 -d30s http://127.0.0.1:8080/
Running 30s test @ http://127.0.0.1:8080/
  12 threads and 10000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   487.42ms  143.07ms 993.21ms   79.94%
    Req/Sec     2.31k     1.92k    8.32k    66.02%
  604420 requests in 30.10s, 80.70MB read
  Socket errors: connect 11, read 0, write 0, timeout 0
Requests/sec:  20080.15
Transfer/sec:      2.68MB
*/

int main()
{
    int epoll_fd=epoll_create1(0); //创建epoll实例
    if(epoll_fd==-1)
    {
        perror("epoll error");
        return EXIT_FAILURE;
    }

    int server_fd=socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK,0);  //创建listen socket
    if(server_fd==-1)
    {
        perror("socket");
        return EXIT_FAILURE;
    }

    //监听地址+端口
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8080); //这里监听8080端口，涉及到一个字节序的转换
    addr.sin_addr.s_addr=htonl(INADDR_ANY);//监听地址 INADDR_ANY指主机任意一块网卡的地址


    if(bind(server_fd,(struct sockaddr*)&addr,sizeof(addr))==-1)
    {
        perror("bind");
        return EXIT_FAILURE;
    }

    if(listen(server_fd,SOMAXCONN)==-1)
    {
        perror("listen");
        return EXIT_FAILURE;
    }

    char ip1[INET_ADDRSTRLEN] ; //就是16 ip地址长度
    inet_ntop(AF_INET,&addr.sin_addr,ip1,sizeof(ip1));
    cout<<"socket is listening on port "<<ntohs(addr.sin_port)<<" address "<<ip1<<endl;


    //下面先将监听socket交给epoll管理

    struct epoll_event event;
    event.events=EPOLLIN|EPOLLET; //监听读事件，边缘触发模式
    event.data.fd=server_fd;
    if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_fd,&event)==-1)
    {
        perror("epollctl");
        return EXIT_FAILURE;
    }

    struct epoll_event events[512]; //其实这里还可以开更大，看计算机性能吧
    while(true)
    {     
       int nums_events=epoll_wait(epoll_fd,events,512,-1);
       if(nums_events==-1)
       {
        perror("epollwait");
        return EXIT_FAILURE;
       }

       for(int i=0;i<nums_events;i++)
       {
         if(events[i].data.fd==server_fd) // 连接请求 前面已经将server_fd注册到epoll里面了。
         {    
            //处理链接请求要做什么事？
            //首先要知道，这里选用边缘触发模式，只会触发一次，所以要一次性处理完所有事件
            while(true)
            {
               struct sockaddr_in clientaddr;
               memset(&clientaddr,0,sizeof(clientaddr));
               socklen_t len=sizeof(clientaddr);
               //1.accept 接受这个链接
               int client_fd=accept(server_fd,(struct sockaddr *)&clientaddr,&len);
               
               if(client_fd==-1)
               {
                if(errno==EAGAIN||errno==EWOULDBLOCK)
                break;
                else{
                    perror("accept");
                    break;
                }
               }
        
               //2.如果要实现高性能网络服务器的话，这里要将每一个客户段设置为1非阻塞
               //阻塞会让你的线程卡死在某个客户端的超长读事件中，拖垮整个进程，尤其是在这种单线程上

               int flags=fcntl(client_fd,F_GETFL,0);
               if(flags==-1)
               {
                perror("fcntl fail");
                exit(EXIT_FAILURE);
               }
               if(fcntl(client_fd,F_SETFL,flags | O_NONBLOCK )==-1)
               {
                perror("fcntl F_SETFL");
                exit(EXIT_FAILURE);
               }
               
               //打印客户端信息，便于调试
               char client_ip[INET_ADDRSTRLEN] ; //就是16 ip地址长度
               inet_ntop(AF_INET,&clientaddr.sin_addr,client_ip,sizeof(client_ip));
               cout<<"New Connection From "<<client_ip<<":"<<ntohs(clientaddr.sin_port)<<endl;

               //将客户端所对应的文件描述符加入 EPOLL 中
               struct epoll_event clientevent;
               clientevent.events=EPOLLIN | EPOLLRDHUP | EPOLLET;
               clientevent.data.fd=client_fd;
               if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_fd,&clientevent)==-1)
               {
                  perror("epollctl client");
                  close(client_fd);
                  continue; 
               }
               //这里和客户端对话的socket已经注册完成，当有读写事件时便可以继续处理。
            }  
         }
         else{
            //如果不是服务器监听socket的事件，那就是对话socket的事件了
            //这里有几个可能的事件要处理

            //1.处理关闭连接的事件
            if(events[i].events & EPOLLRDHUP){
                cout<<"Connection closed by client fd:"<<events[i].data.fd<<endl;
                //这里要把这个fd从epoll中删除 !!必须删除，而且操作顺序不能颠倒
                //为什么不能颠倒？ 在单线程可能体现较差，但是在多线程中非常容易出现fd刚被关闭然后
                //又被重用了，哦那个这时候你给他epoll_ctl_del 一下，废掉了
                if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,events[i].data.fd,nullptr)==-1)
                {
                    perror("epollctl del failed");
                    continue;
                }
                //关闭文件描述符
                close(events[i].data.fd);
                continue;
            }

            //2.处理读事件
            if(events[i].events & EPOLLIN)
            {
               char buffer[4096];
               ssize_t read_bytes; //就是long
               
               while((read_bytes=read(events[i].data.fd,buffer,4095))>0)
               {
                  buffer[read_bytes] = '\0'; //字符串结束符
                  cout<<"Receive from client_fd"<<events[i].data.fd<<": "
                  <<buffer<<endl;

                  //这里的读事件实际上是指客户端发来的请求，可能是一个URL，也可能是其他请求信息
                  //我们服务器要实现对对应信息的处理
                  //这里指简单回复一个http头，便于测试
                  const char* response = 
                    "HTTP/1.1 200 OK\r\n"
                    "Server: VerySimpleServer\r\n"
                    "Content-Type: text/plain; charset=UTF-8\r\n"
                    "Connection: keep-alive\r\n"
                    "Content-Length: 10\r\n"
                    "\r\n"
                    "0123456789";  // 10字节内容
                  send(events[i].data.fd, response, strlen(response), 0);

                  if(read_bytes==-1)
                  {
                    if(errno != EAGAIN && errno != EWOULDBLOCK )
                    {
                        perror("Read ");
                        if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,events[i].data.fd,nullptr)==-1)
                        {
                            perror("epollctl del failed");
                            continue;
                        }
                        //关闭文件描述符
                        close(events[i].data.fd);
                    }
                  }
                  else if(read_bytes==0)
                  {
                    cout<<"Connection closed by client fd:"<<events[i].data.fd<<endl;
                    if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,events[i].data.fd,nullptr)==-1)
                    {
                        perror("epollctl del failed");
                        continue;
                    }
                    //关闭文件描述符
                    close(events[i].data.fd);
                  }
               }
            }
            
            //3.错误事件
            if(events[i].events & EPOLLERR){
                cerr<<"error on fd: "<<events[i].data.fd<<endl;
                if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,events[i].data.fd,nullptr)==-1)
                {
                    perror("epollctl del failed");
                    continue;
                }
                //关闭文件描述符
                close(events[i].data.fd);
            }
         }
       }
    }

    close(epoll_fd);
    close(server_fd);
    return EXIT_SUCCESS;
}